/*
 *  Copyright 2011 Quytelda K. Gaiwin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.tamalin.nighthawk.engine;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

/**
 * The CipherEngine class is the encryption engine that handles all encryption and decryption operations
 * within the nighthawk program.
 * @author Quytelda K. Gaiwin
 */
public class CipherEngine
{
	/**
	 * The CipherEngine constructor is hidden to comply more fully with Java cryptographic library standards.
	 * @param alg
	 */
	protected CipherEngine(String alg, Context c)
	{
		algorithm = alg;
		this.context = c;
	}

	/**
	 * Returns a new instance of a CipherEngine object, initialized with the given algorithm.
	 * @param alg The encryption algorithm to use
	 * @return A new instance of CipherEngine
	 */
	public static CipherEngine getInstance(String alg, Context c)
	{
		return new CipherEngine(alg, c);
	}

	/**
	 * Initializes the CipherEngine for a single operation, with a combination of data, a password, and a mode.
	 * @param dt the data to either encrypt or decrypt.
	 * @param pwd the password to use when operating on the data
	 * @param m the cipher mode, which can be equal to either <code>Cipher.DECRYPT_MODE</code> or <code>Cipher.ENCRYPT_MODE</code>
	 */
	public void init(byte[] dt, char[] pwd, int m)
	{
		data = dt;
		password = pwd;
		Log.d(CipherEngine.class.getName(), "Init pwd Length: " + password.length);
		mode = m;
	}

	/**
	 * Starts the Encryption engine to operate on the data previously provided in the init method.
	 * @return the result of the operation
	 */
	public byte[] start()
	{
		if(password == null || password.length == 0)
		{
			return null;
		}
		
		return run();
	}

	/**
	 * Carries out the operation with the engine's current data, password, and mode.
	 * @return the result of the operation.
	 */
	private byte[] run()
	{
		byte[] output = null;

		if (data == null)
			return null;

		try
		{
			Cipher cipher = Cipher.getInstance(algorithm);
			Key key = computeKey(password);

			cipher.init(mode, key);

			// do encryption
			output = cipher.doFinal(data);
		}
		catch (NoSuchAlgorithmException e)
		{
			Toast.makeText(context, "Algorithm not found.", Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		catch (NoSuchPaddingException e)
		{
			Toast.makeText(context, "Padding scheme is unknown.", Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		catch (InvalidKeyException e)
		{
			Toast.makeText(context, "Invalid key!", Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		catch (IllegalBlockSizeException e)
		{
			Toast.makeText(context, "Block size is illegal.", Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		catch (BadPaddingException e)
		{
			Toast.makeText(context, "Wrong password or invalid padding scheme.", Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		return output;
	}

	/**
	 * Generates a SecretKey object from the 20 byte MD5 hash of the provided password.
	 * @param pwd the password to use for generating the secret key.
	 * @return the SecretKey object generated from the hash of the password.
	 * @throws NoSuchAlgorithmException A no such algorithm exception is thrown if the MessageDigest algorithm is not found.
	 */
	public SecretKey computeKey(char[] pwd) throws NoSuchAlgorithmException
	{
		Log.d(CipherEngine.class.getName(), "Password Length: " + pwd.length);
		/* Digest the password into a sequence of bytes the right length. */
        MessageDigest digest = MessageDigest.getInstance("MD5");
        for (char character : pwd)
            digest.update((byte) character);

        byte[] passKey = digest.digest();
        Log.d(CipherEngine.class.getName(), "passKey Length: " + passKey.length);

        /* Create the key key. */
        return new SecretKeySpec(passKey, algorithm);
	}

	private String algorithm;
	private byte[] data;
	private char[] password;
	private int mode;
	private Context context;
}